<?php
namespace ZyucMini\Core;

/**
 * HTTP客户端类 - 负责第三方HTTP请求
 * 用于发起第三方HTTP请求，例如请求大模型API、数据接口等
 * 
 * @package ZyucMini\Core
 * @author 黄凯
 */
class HttpClient
{
    /**
     * 默认超时时间(秒)
     * @var int
     */
    protected $timeout = 10;

    /**
     * 请求头数组
     * @var array
     */
    protected $headers = [];
    
    /**
     * 最大重试次数
     * @var int
     */
    protected $maxRetries = 3;
    
    /**
     * 是否验证SSL证书
     * @var bool
     */
    protected $verifySSL = true;
    
    /**
     * 允许的协议列表
     * @var array
     */
    protected $allowedProtocols = ['http', 'https'];
    
    /**
     * 最大请求大小(字节)
     * @var int
     */
    protected $maxRequestSize = 10485760; // 10MB

    /**
     * 设置请求头
     * @param string $name 请求头名称
     * @param string $value 请求头值
     * @return HttpClient
     */
    public function setHeader($name, $value)
    {
        $this->headers[$name] = $value;
        return $this;
    }
    
    /**
     * 批量设置请求头
     * @param array $headers 请求头数组
     * @return HttpClient
     */
    public function setHeaders($headers)
    {
        $this->headers = array_merge($this->headers, $headers);
        return $this;
    }

    /**
     * 设置超时时间
     * @param int $seconds 超时时间(秒)
     * @return HttpClient
     */
    public function setTimeout($seconds)
    {
        $this->timeout = max(1, (int)$seconds);
        return $this;
    }
    
    /**
     * 设置最大重试次数
     * @param int $retries 重试次数
     * @return HttpClient
     */
    public function setMaxRetries($retries)
    {
        $this->maxRetries = max(0, (int)$retries);
        return $this;
    }
    
    /**
     * 设置SSL证书验证
     * @param bool $verify 是否验证SSL证书
     * @return HttpClient
     */
    public function setSSLVerify($verify)
    {
        $this->verifySSL = (bool)$verify;
        return $this;
    }
    
    /**
     * 设置允许的协议
     * @param array $protocols 协议列表
     * @return HttpClient
     */
    public function setAllowedProtocols($protocols)
    {
        $this->allowedProtocols = $protocols;
        return $this;
    }

    /**
     * 发送GET请求
     * @param string $url 请求URL
     * @param array $params 请求参数
     * @return array
     * @throws \Exception
     */
    public function get($url, $params = [])
    {
        $this->validateUrl($url);
        
        if (!empty($params)) {
            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
        }
        return $this->request('GET', $url);
    }

    /**
     * 发送POST请求
     * @param string $url 请求URL
     * @param array $data 请求数据
     * @param bool $asJson 是否以JSON格式发送
     * @return array
     * @throws \Exception
     */
    public function post($url, $data = [], $asJson = false)
    {
        $this->validateUrl($url);
        
        if ($asJson) {
            $this->setHeader('Content-Type', 'application/json');
            $jsonData = json_encode($data);
            if ($jsonData === false) {
                throw new \InvalidArgumentException('JSON encoding failed: ' . json_last_error_msg());
            }
            $this->validateRequestSize($jsonData);
            return $this->request('POST', $url, $jsonData);
        }
        
        $this->validateRequestSize($data);
        return $this->request('POST', $url, $data);
    }

    /**
     * 发送HTTP请求
     * @param string $method 请求方法
     * @param string $url 请求URL
     * @param mixed $data 请求数据
     * @return array
     * @throws \Exception
     */
    protected function request($method, $url, $data = [])
    {
        $attempts = 0;
        $lastError = null;
        
        while ($attempts <= $this->maxRetries) {
            try {
                return $this->executeRequest($method, $url, $data);
            } catch (\Exception $e) {
                $lastError = $e;
                $attempts++;
                
                if ($attempts <= $this->maxRetries) {
                    // 等待一段时间后重试
                    usleep(100000 * $attempts); // 递增等待时间
                    continue;
                }
                
                throw $e;
            }
        }
        
        throw $lastError;
    }
    
    /**
     * 执行HTTP请求
     * @param string $method 请求方法
     * @param string $url 请求URL
     * @param mixed $data 请求数据
     * @return array
     * @throws \Exception
     */
    protected function executeRequest($method, $url, $data = [])
    {
        $ch = curl_init();
        
        try {
            // 设置基本选项
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 禁用自动重定向，防止SSRF
            curl_setopt($ch, CURLOPT_MAXREDIRS, 0); // 禁止重定向
            
            // SSL证书验证设置
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verifySSL);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->verifySSL ? 2 : 0);
            
            // 设置用户代理
            curl_setopt($ch, CURLOPT_USERAGENT, 'ZyucMini HttpClient/1.0');
            
            // 设置请求方法
            if ($method === 'POST') {
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            } elseif ($method === 'PUT') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            } elseif ($method === 'DELETE') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
            }
            
            // 设置请求头
            if (!empty($this->headers)) {
                $headers = [];
                foreach ($this->headers as $name => $value) {
                    $headers[] = "$name: $value";
                }
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            }
            
            // 执行请求
            $response = curl_exec($ch);
            
            if ($response === false) {
                $error = curl_error($ch);
                $errorCode = curl_errno($ch);
                throw new \Exception("HTTP请求失败 (错误代码: $errorCode)");
            }
            
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
            
            return [
                'status' => $httpCode,
                'body' => $response,
                'headers' => $this->parseResponseHeaders($ch),
                'content_type' => $contentType
            ];
            
        } finally {
            curl_close($ch);
        }
    }
    
    /**
     * 验证URL安全性
     * @param string $url URL地址
     * @throws \InvalidArgumentException
     */
    protected function validateUrl($url)
    {
        if (empty($url)) {
            throw new \InvalidArgumentException('URL不能为空');
        }
        
        $parsedUrl = parse_url($url);
        if ($parsedUrl === false) {
            throw new \InvalidArgumentException('无效的URL格式');
        }
        
        // 检查协议
        if (!isset($parsedUrl['scheme']) || !in_array($parsedUrl['scheme'], $this->allowedProtocols)) {
            throw new \InvalidArgumentException('不允许的协议: ' . ($parsedUrl['scheme'] ?? '未知'));
        }
        
        // 检查是否为内网地址
        if (isset($parsedUrl['host'])) {
            $host = $parsedUrl['host'];
            if ($this->isPrivateIP($host)) {
                throw new \InvalidArgumentException('不允许访问内网地址');
            }
        }
    }
    
    /**
     * 检查是否为内网IP
     * @param string $host 主机地址
     * @return bool
     */
    protected function isPrivateIP($host)
    {
        // 检查是否为IP地址
        if (filter_var($host, FILTER_VALIDATE_IP)) {
            return !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
        }
        
        // 检查是否为内网域名
        $privateDomains = ['localhost', '127.0.0.1', '0.0.0.0', '::1'];
        return in_array(strtolower($host), $privateDomains);
    }
    
    /**
     * 验证请求大小
     * @param mixed $data 请求数据
     * @throws \InvalidArgumentException
     */
    protected function validateRequestSize($data)
    {
        $size = is_string($data) ? strlen($data) : strlen(serialize($data));
        if ($size > $this->maxRequestSize) {
            throw new \InvalidArgumentException('请求数据过大，超过限制: ' . $this->maxRequestSize . ' 字节');
        }
    }
    
    /**
     * 解析响应头
     * @param resource $ch cURL句柄
     * @return array
     */
    protected function parseResponseHeaders($ch)
    {
        $headers = [];
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $headerString = substr(curl_getinfo($ch, CURLINFO_HEADER_OUT), 0, $headerSize);
        
        if ($headerString) {
            $headerLines = explode("\r\n", $headerString);
            foreach ($headerLines as $line) {
                if (strpos($line, ':') !== false) {
                    [$name, $value] = explode(':', $line, 2);
                    $headers[trim($name)] = trim($value);
                }
            }
        }
        
        return $headers;
    }
    
    /**
     * 发送PUT请求
     * @param string $url 请求URL
     * @param mixed $data 请求数据
     * @return array
     * @throws \Exception
     */
    public function put($url, $data = [])
    {
        $this->validateUrl($url);
        $this->validateRequestSize($data);
        return $this->request('PUT', $url, $data);
    }
    
    /**
     * 发送DELETE请求
     * @param string $url 请求URL
     * @return array
     * @throws \Exception
     */
    public function delete($url)
    {
        $this->validateUrl($url);
        return $this->request('DELETE', $url);
    }
    
    /**
     * 获取当前配置
     * @return array
     */
    public function getConfig()
    {
        return [
            'timeout' => $this->timeout,
            'maxRetries' => $this->maxRetries,
            'verifySSL' => $this->verifySSL,
            'allowedProtocols' => $this->allowedProtocols,
            'maxRequestSize' => $this->maxRequestSize,
            'headers' => $this->headers
        ];
    }
}